const { v4: uuidv4 } = require('uuid');

class ConnectionManager {
  constructor() {
    this.ws = null;
    this.clientId = this.generateClientId();
    this.serverUrl = null;
    this.wsUrl = null;
    this.connected = false;
    this.eventHandlers = {};
    this.discoverySocket = null;
    this.heartbeatInterval = null;
    this.peerConnection = null;
    this.localStream = null;
    this.remoteStream = null;
    this.currentCallPeer = null;
    this.iceCandidatesQueue = [];
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 10;
    this.reconnectDelay = 1000; // Start with 1 second
    this.isManualDisconnect = false;
  }

  generateClientId() {
    const stored = localStorage.getItem('lanchat-client-id');
    if (stored) return stored;

    const id = uuidv4();
    localStorage.setItem('lanchat-client-id', id);
    return id;
  }

  on(event, handler) {
    if (!this.eventHandlers[event]) {
      this.eventHandlers[event] = [];
    }
    this.eventHandlers[event].push(handler);
  }

  off(event, handler) {
    if (!this.eventHandlers[event]) {
      return;
    }

    if (handler) {
      // 移除特定处理器
      this.eventHandlers[event] = this.eventHandlers[event].filter(h => h !== handler);
    } else {
      // 移除所有处理器
      this.eventHandlers[event] = [];
    }
  }

  emit(event, data) {
    if (this.eventHandlers[event]) {
      this.eventHandlers[event].forEach(handler => handler(data));
    }
  }

  async connect() {
    try {
      // Discover server with retry mechanism
      let retries = 3;
      while (retries > 0 && !this.wsUrl) {
        await this.discoverServer();
        if (!this.wsUrl) {
          retries--;
          if (retries > 0) {
            console.log(`Server discovery failed, retrying... (${retries} attempts left)`);
            await new Promise(resolve => setTimeout(resolve, 2000));
          }
        }
      }

      if (!this.wsUrl) {
        console.error('Could not discover server after 3 attempts');
        this.emit('discovery-failed', { error: 'Server not found after 3 attempts' });
        return;
      }

      // Connect to WebSocket
      this.ws = new WebSocket(this.wsUrl);

      this.ws.onopen = () => {
        console.log('Connected to server');
        this.connected = true;

        // Reset reconnection state on successful connection
        this.reconnectAttempts = 0;
        this.reconnectDelay = 1000;
        this.isManualDisconnect = false;

        // Register with server
        this.ws.send(JSON.stringify({
          type: 'register',
          clientId: this.clientId,
          name: this.getDeviceName()
        }));

        this.emit('connected');
        this.startHeartbeat();
      };

      this.ws.onmessage = (event) => {
        this.handleMessage(JSON.parse(event.data));
      };

      this.ws.onclose = () => {
        console.log('Disconnected from server');
        this.connected = false;
        this.emit('disconnected');
        this.stopHeartbeat();

        // Attempt reconnection with exponential backoff (if not manual disconnect)
        if (!this.isManualDisconnect) {
          this.attemptReconnect();
        }
      };

      this.ws.onerror = (error) => {
        console.error('WebSocket error:', error);
      };

    } catch (error) {
      console.error('Connection error:', error);
    }
  }

  async discoverServer() {
    return new Promise((resolve) => {
      const dgram = window.require('dgram');
      const socket = dgram.createSocket('udp4');

      socket.on('message', (msg, rinfo) => {
        try {
          const data = JSON.parse(msg.toString());

          if (data.type === 'server-announce' || data.type === 'discovery-response') {
            this.serverUrl = `http://${data.ip}:${data.httpPort}`;
            this.wsUrl = `ws://${data.ip}:${data.wsPort}`;

            console.log('Server discovered:', this.serverUrl);
            socket.close();
            resolve();
          }
        } catch (error) {
          // Ignore invalid messages
        }
      });

      socket.bind(3002, () => {
        // Send discovery request
        const request = JSON.stringify({ type: 'discovery-request' });
        socket.setBroadcast(true);
        socket.send(request, 3002, '255.255.255.255');
      });

      // Timeout after 10 seconds
      setTimeout(() => {
        socket.close();
        resolve();
      }, 10000);
    });
  }

  handleMessage(message) {
    switch (message.type) {
      case 'registered':
        console.log('Registered with server:', message.clientId);
        break;

      case 'peer-list':
        this.emit('peer-list', message.peers);
        break;

      case 'message':
        this.emit('message', {
          from: message.from,
          to: this.clientId,
          content: message.content,
          type: message.messageType,
          timestamp: message.timestamp,
          status: 'received'
        });
        break;

      case 'message-delivered':
        this.emit('message-delivered', {
          to: message.to,
          timestamp: message.timestamp
        });
        break;

      case 'message-failed':
        this.emit('message-failed', {
          to: message.to,
          reason: message.reason,
          timestamp: message.timestamp
        });
        break;

      case 'file-incoming':
        this.emit('file-incoming', {
          from: message.from,
          to: this.clientId,
          fileName: message.fileName,
          fileSize: message.fileSize,
          fileId: message.fileId,
          timestamp: message.timestamp,
          status: 'pending'
        });
        break;

      case 'webrtc-signal':
        this.handleWebRTCSignal(message);
        break;

      case 'incoming-call':
        this.emit('incoming-call', {
          from: message.from,
          callType: message.callType
        });
        break;

      case 'call-ringing':
        this.emit('call-ringing', {
          to: message.to
        });
        break;

      case 'call-failed':
        this.emit('call-failed', {
          to: message.to,
          reason: message.reason
        });
        break;

      case 'heartbeat-ack':
        // Heartbeat acknowledged
        break;

      default:
        console.log('Unknown message type:', message.type);
    }
  }

  sendMessage(to, content, type = 'text') {
    if (!this.ws || !this.connected) {
      console.error('Not connected');
      return;
    }

    const messageId = uuidv4();

    const message = {
      type: 'message',
      to: to,
      from: this.clientId,
      content: content,
      messageType: type,
      messageId: messageId
    };

    this.ws.send(JSON.stringify(message));

    // Emit sent message for UI update
    this.emit('message', {
      from: this.clientId,
      to: to,
      content: content,
      type: type,
      timestamp: Date.now(),
      status: 'sent',
      messageId: messageId
    });
  }

  async loadMessageHistory(peerId, limit = 100, offset = 0) {
    if (!this.serverUrl) {
      console.error('Server URL not set');
      return { messages: [], pagination: null };
    }

    try {
      const response = await fetch(
        `${this.serverUrl}/api/messages/${peerId}?limit=${limit}&offset=${offset}`
      );
      const data = await response.json();
      return {
        messages: data.messages || [],
        pagination: data.pagination || null
      };
    } catch (error) {
      console.error('Error loading message history:', error);
      return { messages: [], pagination: null };
    }
  }

  async sendFile(to, file) {
    if (!this.serverUrl) {
      console.error('Server URL not set');
      return;
    }

    // Validate file size (100GB limit)
    const MAX_FILE_SIZE = 100 * 1024 * 1024 * 1024; // 100GB
    if (file.size > MAX_FILE_SIZE) {
      this.emit('file-transfer-failed', {
        error: 'File size exceeds 100GB limit',
        fileName: file.name
      });
      return;
    }

    const fileId = uuidv4();

    // Send file metadata first
    this.ws.send(JSON.stringify({
      type: 'file-meta',
      to: to,
      from: this.clientId,
      fileName: file.name,
      fileSize: file.size,
      fileId: fileId
    }));

    // Emit file transfer start for UI update
    this.emit('file-transfer-start', {
      from: this.clientId,
      to: to,
      fileName: file.name,
      fileSize: file.size,
      fileId: fileId,
      status: 'uploading'
    });

    // Upload file to server with progress tracking
    const formData = new FormData();
    formData.append('file', file);
    formData.append('fileId', fileId);
    formData.append('peerId', to);
    formData.append('senderId', this.clientId);

    try {
      // Use XMLHttpRequest for progress tracking
      const xhr = new XMLHttpRequest();

      // Track upload progress
      xhr.upload.addEventListener('progress', (e) => {
        if (e.lengthComputable) {
          const progress = e.loaded / e.total;
          this.emit('file-transfer-progress', {
            fileId: fileId,
            progress: progress,
            loaded: e.loaded,
            total: e.total
          });
        }
      });

      // Handle completion
      const uploadPromise = new Promise((resolve, reject) => {
        xhr.addEventListener('load', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            try {
              const result = JSON.parse(xhr.responseText);
              resolve(result);
            } catch (error) {
              reject(new Error('Invalid server response'));
            }
          } else {
            reject(new Error(`Upload failed with status ${xhr.status}`));
          }
        });

        xhr.addEventListener('error', () => {
          reject(new Error('Network error during upload'));
        });

        xhr.addEventListener('abort', () => {
          reject(new Error('Upload cancelled'));
        });
      });

      xhr.open('POST', `${this.serverUrl}/api/upload`);
      xhr.send(formData);

      const result = await uploadPromise;

      if (result.success) {
        console.log('File uploaded:', result);
        this.emit('file-transfer-complete', {
          fileId: fileId,
          status: 'completed'
        });
      } else {
        throw new Error(result.error || 'Upload failed');
      }

    } catch (error) {
      console.error('File upload error:', error);
      this.emit('file-transfer-failed', {
        fileId: fileId,
        status: 'failed',
        error: error.message
      });
    }
  }

  async downloadFile(fileId, fileName) {
    if (!this.serverUrl) {
      console.error('Server URL not set');
      return;
    }

    try {
      // Use XMLHttpRequest for progress tracking
      const xhr = new XMLHttpRequest();

      xhr.addEventListener('progress', (e) => {
        if (e.lengthComputable) {
          const progress = e.loaded / e.total;
          this.emit('file-download-progress', {
            fileId: fileId,
            progress: progress,
            loaded: e.loaded,
            total: e.total
          });
        }
      });

      const downloadPromise = new Promise((resolve, reject) => {
        xhr.addEventListener('load', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(xhr.response);
          } else {
            reject(new Error(`Download failed with status ${xhr.status}`));
          }
        });

        xhr.addEventListener('error', () => {
          reject(new Error('Network error during download'));
        });

        xhr.addEventListener('abort', () => {
          reject(new Error('Download cancelled'));
        });
      });

      xhr.open('GET', `${this.serverUrl}/api/download/${fileId}`);
      xhr.responseType = 'blob';
      xhr.send();

      const blob = await downloadPromise;

      // Create download link
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = fileName;
      document.body.appendChild(a);
      a.click();
      window.URL.revokeObjectURL(url);
      document.body.removeChild(a);

      this.emit('file-download-complete', {
        fileId: fileId,
        fileName: fileName
      });

    } catch (error) {
      console.error('File download error:', error);
      this.emit('file-download-failed', {
        fileId: fileId,
        error: error.message
      });
    }
  }

  async startCall(to, type) {
    this.currentCallPeer = to;

    // Setup call timeout (60 seconds)
    if (!this.callTimeouts) {
      this.callTimeouts = new Map();
    }

    const callTimeout = setTimeout(() => {
      if (this.currentCallPeer === to && !this.peerConnection) {
        console.log('Call timeout - no response from peer');
        this.emit('call-timeout', { to });
        this.endCall();
      }
    }, 60000);

    this.callTimeouts.set(to, callTimeout);

    // Send call request first
    this.ws.send(JSON.stringify({
      type: 'webrtc-signal',
      to: to,
      from: this.clientId,
      signalType: 'call-request',
      signal: { callType: type }
    }));

    // Setup peer connection
    await this.setupPeerConnection(type);

    // Create offer
    const offer = await this.peerConnection.createOffer();
    await this.peerConnection.setLocalDescription(offer);

    // Send offer
    this.ws.send(JSON.stringify({
      type: 'webrtc-signal',
      to: to,
      from: this.clientId,
      signalType: 'offer',
      signal: offer
    }));
  }

  async acceptCall(from, callType) {
    this.currentCallPeer = from;

    // Send acceptance
    this.ws.send(JSON.stringify({
      type: 'webrtc-signal',
      to: from,
      from: this.clientId,
      signalType: 'call-accepted',
      signal: { accepted: true }
    }));

    // Setup peer connection
    await this.setupPeerConnection(callType);
  }

  rejectCall(from) {
    this.ws.send(JSON.stringify({
      type: 'webrtc-signal',
      to: from,
      from: this.clientId,
      signalType: 'call-rejected',
      signal: { accepted: false }
    }));
  }

  endCall() {
    // Clear ICE candidates queue to prevent memory leak
    this.iceCandidatesQueue = [];

    // Clear call timeout if exists
    if (this.callTimeouts && this.currentCallPeer) {
      const timeout = this.callTimeouts.get(this.currentCallPeer);
      if (timeout) {
        clearTimeout(timeout);
        this.callTimeouts.delete(this.currentCallPeer);
      }
    }

    // Stop all tracks
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => track.stop());
      this.localStream = null;
    }

    // Close peer connection and remove event listeners
    if (this.peerConnection) {
      this.peerConnection.onicecandidate = null;
      this.peerConnection.ontrack = null;
      this.peerConnection.onconnectionstatechange = null;
      this.peerConnection.close();
      this.peerConnection = null;
    }

    // Notify the other peer
    if (this.currentCallPeer && this.ws && this.connected) {
      try {
        this.ws.send(JSON.stringify({
          type: 'webrtc-signal',
          to: this.currentCallPeer,
          from: this.clientId,
          signalType: 'call-end',
          signal: {}
        }));
      } catch (error) {
        console.error('Failed to send call-end signal:', error);
      }
    }

    this.currentCallPeer = null;
    this.remoteStream = null;
    this.emit('call-ended');
  }

  async setupPeerConnection(callType) {
    // Get user media
    try {
      this.localStream = await navigator.mediaDevices.getUserMedia({
        audio: {
          echoCancellation: true,
          noiseSuppression: true,
          autoGainControl: true
        },
        video: callType === 'video' ? {
          width: { ideal: 1280 },
          height: { ideal: 720 },
          frameRate: { ideal: 30 }
        } : false
      });

      this.emit('local-stream', this.localStream);
    } catch (error) {
      console.error('Error accessing media devices:', error);
      throw error;
    }

    // Create peer connection with improved configuration
    const rtcConfig = {
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        { urls: 'stun:stun1.l.google.com:19302' },
        { urls: 'stun:stun2.l.google.com:19302' },
        { urls: 'stun:stun3.l.google.com:19302' }
        // Add TURN servers here if needed for better NAT traversal
        // {
        //   urls: 'turn:your-turn-server:3478',
        //   username: 'user',
        //   credential: 'pass'
        // }
      ],
      iceTransportPolicy: 'all',
      iceCandidatePoolSize: 10
    };

    this.peerConnection = new RTCPeerConnection(rtcConfig);

    // Add local stream to peer connection
    this.localStream.getTracks().forEach(track => {
      this.peerConnection.addTrack(track, this.localStream);
    });

    // Handle ICE candidates
    this.peerConnection.onicecandidate = (event) => {
      if (event.candidate) {
        this.ws.send(JSON.stringify({
          type: 'webrtc-signal',
          to: this.currentCallPeer,
          from: this.clientId,
          signalType: 'ice-candidate',
          signal: event.candidate
        }));
      }
    };

    // Handle remote stream
    this.peerConnection.ontrack = (event) => {
      if (event.streams && event.streams[0]) {
        this.remoteStream = event.streams[0];
        this.emit('remote-stream', this.remoteStream);
      }
    };

    // Handle connection state changes
    this.peerConnection.onconnectionstatechange = () => {
      console.log('Connection state:', this.peerConnection.connectionState);
      if (this.peerConnection.connectionState === 'disconnected' ||
          this.peerConnection.connectionState === 'failed') {
        this.endCall();
      }
    };

    // Process queued ICE candidates
    while (this.iceCandidatesQueue.length > 0) {
      const candidate = this.iceCandidatesQueue.shift();
      await this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
    }
  }

  async handleWebRTCSignal(message) {
    const { signalType, signal, from } = message;

    try {
      switch (signalType) {
        case 'offer':
          if (this.peerConnection) {
            await this.peerConnection.setRemoteDescription(new RTCSessionDescription(signal));
            const answer = await this.peerConnection.createAnswer();
            await this.peerConnection.setLocalDescription(answer);

            this.ws.send(JSON.stringify({
              type: 'webrtc-signal',
              to: from,
              from: this.clientId,
              signalType: 'answer',
              signal: answer
            }));
          }
          break;

        case 'answer':
          if (this.peerConnection) {
            await this.peerConnection.setRemoteDescription(new RTCSessionDescription(signal));
          }
          break;

        case 'ice-candidate':
          if (this.peerConnection && this.peerConnection.remoteDescription) {
            await this.peerConnection.addIceCandidate(new RTCIceCandidate(signal));
          } else {
            // Queue ICE candidate if remote description is not set yet
            this.iceCandidatesQueue.push(signal);
          }
          break;

        case 'call-end':
          this.endCall();
          break;

        default:
          console.log('Unknown WebRTC signal type:', signalType);
      }
    } catch (error) {
      console.error('Error handling WebRTC signal:', error);
    }
  }

  getLocalStream() {
    return this.localStream;
  }

  getRemoteStream() {
    return this.remoteStream;
  }

  startHeartbeat() {
    this.heartbeatInterval = setInterval(() => {
      if (this.ws && this.connected) {
        this.ws.send(JSON.stringify({ type: 'heartbeat' }));
      }
    }, 30000); // Every 30 seconds
  }

  stopHeartbeat() {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
    }
  }

  getDeviceName() {
    const stored = localStorage.getItem('lanchat-device-name');
    if (stored) return stored;

    try {
      // Try to get hostname via Electron preload API
      if (window.electron && window.electron.getHostname) {
        window.electron.getHostname().then(result => {
          if (result.success) {
            localStorage.setItem('lanchat-device-name', result.hostname);
          }
        });
      }

      // Fallback: try require (will fail in browser/with contextIsolation)
      const os = window.require('os');
      const name = os.hostname();
      localStorage.setItem('lanchat-device-name', name);
      return name;
    } catch (error) {
      // Fallback to generated name
      const defaultName = 'User-' + Math.random().toString(36).substr(2, 9);
      localStorage.setItem('lanchat-device-name', defaultName);
      return defaultName;
    }
  }

  attemptReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.log('Max reconnection attempts reached');
      this.emit('reconnect-failed', {
        message: 'Could not reconnect to server after multiple attempts'
      });
      return;
    }

    this.reconnectAttempts++;
    const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1), 30000);

    console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
    this.emit('reconnecting', { attempt: this.reconnectAttempts, delay });

    setTimeout(() => {
      this.connect();
    }, delay);
  }

  disconnect() {
    this.isManualDisconnect = true;
    this.stopHeartbeat();

    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }

    this.connected = false;
  }
}

export default ConnectionManager;
